package org.com.andy.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.com.andy.constant.ProcessConstants;
import org.com.andy.domain.R;
import org.com.andy.domain.dto.FlowBizKeyDto;
import org.com.andy.domain.dto.FlowQueryVo;
import org.com.andy.domain.dto.FlowStartDto;
import org.com.andy.domain.dto.FlowTaskDto;
import org.com.andy.domain.vo.ProcessDefinitionVo;
import org.com.andy.factory.FlowServiceFactory;
import org.com.andy.service.IFlowDefinitionService;
import org.com.andy.service.ISysDeployFormService;
import org.com.andy.web.domain.AjaxResult;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.flowable.spring.boot.FlowableProperties;
import org.flowable.spring.boot.ProcessEngineAutoConfiguration;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipInputStream;

/**
 * 流程定义
 *
 * @author Tony
 * @date 2021-04-03
 */
@Service
@Slf4j
public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFlowDefinitionService {

//    @Autowired
//    private ISysDeployFormService sysDeployFormService;

//    @Autowired
//    private FlowDeployMapper flowDeployMapper;

    @Autowired
    private ProcessEngineAutoConfiguration processEngineAutoConfiguration;


    private static final String BPMN_FILE_SUFFIX = ".bpmn";

    @Override
    public boolean exist(String processDefinitionKey) {
        ProcessDefinitionQuery processDefinitionQuery
                = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey);
        long count = processDefinitionQuery.count();
        return count > 0 ? true : false;
    }


    /**
     * 流程定义列表
     *
     * @return 流程定义分页列表数据
     */
//    @Override
//    public Page<ProcessDefinitionVo> list(FlowQueryVo queryVo) {
//        // ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//        // RepositoryService   repositoryService=defaultProcessEngine.getRepositoryService();
//
//        Page<ProcessDefinitionVo> page = new Page<>();
//        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//        // processDefinitionQuery.active();
//        processDefinitionQuery.latestVersion();
//
//        if (StringUtils.isNotBlank(queryVo.getName())) {
//            processDefinitionQuery.processDefinitionNameLike(queryVo.getName());
//        }
//        if (StringUtils.isNotBlank(queryVo.getCategory())) {
//            processDefinitionQuery.processDefinitionCategoryLike(queryVo.getCategory());
//        }
//
//        processDefinitionQuery.orderByProcessDefinitionKey().orderByProcessDefinitionVersion().desc();
//        page.setTotal(processDefinitionQuery.count());
//
//        List<ProcessDefinition> processDefinitions = processDefinitionQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize());
//        List<ProcessDefinitionVo> processDefinitionVos = BeanUtil.copyToList(processDefinitions, ProcessDefinitionVo.class);
//        page.setRecords(processDefinitionVos);
//        return page;
//    }


    /**
     * 导入流程文件
     * <p>
     * 当每个key的流程第一次部署时，指定版本为1。对其后所有使用相同key的流程定义，
     * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义
     *
     * @param name
     * @param category
     * @param in
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessDefinition importFile(String name, String category, InputStream in) {
        Deployment deploy = repositoryService.createDeployment().addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category).deploy();
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
        repositoryService.setProcessDefinitionCategory(definition.getId(), category);
        return definition;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<ProcessDefinition> importZipFiles(String name, String category, ZipInputStream zipInputStream) {
        Deployment deploy = repositoryService.createDeployment().addZipInputStream(zipInputStream).name(name).category(category).deploy();
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).list();
        return list;
    }

    @Override
    public void updateCategory(String procDefId, String category) {
        repositoryService.setProcessDefinitionCategory(procDefId, category);

        List<Task> list = taskService.createTaskQuery().processDefinitionId(procDefId).list();
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(vo -> {
                vo.setCategory(category);
                taskService.saveTask(vo);
            });

        }
    }


    @Override
    public void importByObs() {

    }

    /**
     * 读取xml
     *
     * @param deployId
     * @return
     */
    @Override
    public AjaxResult readXml(String deployId) throws IOException {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
        String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
        return AjaxResult.success("", result);
    }

    /**
     * 读取xml
     *
     * @param deployId
     * @return
     */
    @Override
    public InputStream readImage(String deployId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        // 输出为图片
        return diagramGenerator.generateDiagram(
                bpmnModel,
                "png",
                Collections.emptyList(),
                Collections.emptyList(),
                "宋体",
                "宋体",
                "宋体",
                null,
                1.0,
                false);

    }

    /**
     * 手动部署processes目录文件
     *
     * @return
     */
    @Override
    public List<String> manualDeployment() {
        FlowableProperties flowableProperties = new FlowableProperties();
        try {
            List<Resource> resources = processEngineAutoConfiguration.discoverDeploymentResources(flowableProperties.getProcessDefinitionLocationPrefix(),
                    flowableProperties.getProcessDefinitionLocationSuffixes(),
                    flowableProperties.isCheckProcessDefinitions());

            List<String> ids = new ArrayList<>();
            for (Resource resource : resources) {
                String filename = resource.getDescription();
                InputStream inputStream = resource.getInputStream();
                Deployment deploy = repositoryService.createDeployment().addInputStream(filename + BPMN_FILE_SUFFIX, inputStream).deploy();
                ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
                repositoryService.setProcessDefinitionCategory(definition.getId(), "");
                ids.add(deploy.getId());
            }

            return ids;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据流程定义ID启动流程实例
     *
     * @return
     */
//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public R<FlowTaskDto> startProcessInstanceById(FlowStartDto flowStartDto) {
//        try {
//
//            String procDefId = flowStartDto.getProcDefId();
//            Map<String, Object> variables = flowStartDto.getVariables();
//            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
//                    .singleResult();
//            if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
//                return R.fail("流程已被挂起,请先激活流程");
//            }
//
////            identityService.setAuthenticatedUserId(SecurityUtils.getLoginUser().getUserid().toString());
//            identityService.setAuthenticatedUserId("1");
//            if (ObjectUtil.isEmpty(variables.get(ProcessConstants.PROCESS_INITIATOR)))
//                variables.put(ProcessConstants.PROCESS_INITIATOR, SecurityUtils.getLoginUser().getUserid().toString());
//            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
//            String processInstanceId = processInstance.getProcessInstanceId();
//            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
//            if (processDefinition != null) {
//                task.setCategory(processDefinition.getCategory());
//                task.setDueDate(flowStartDto.getDueDate());
//                taskService.saveTask(task);
//            }
//
//            FlowTaskDto flowTaskDto = new FlowTaskDto();
//            String id = task.getId();
//            flowTaskDto.setTaskId(id);
//            flowTaskDto.setProcInsId(processInstanceId);
//
//            return R.ok(flowTaskDto);
//        } catch (Exception e) {
//            e.printStackTrace();
//            return R.fail("流程启动错误");
//        }
//    }

    /**
     * 根据流程定义ID启动流程实例
     *
     * @param procDefId          流程模板ID
     * @param variables          流程变量
     * @param transientVariables
     * @return
     */
    @Override
    public R<FlowBizKeyDto> startByBusinessKey(String procDefId, String businessKey, Map<String, Object> transientVariables, Map<String, Object> variables) {
        try {
            identityService.setAuthenticatedUserId("admin");
            if (ObjectUtil.isEmpty(variables.get(ProcessConstants.PROCESS_INITIATOR)))
                variables.put(ProcessConstants.PROCESS_INITIATOR, "admin");

            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, businessKey, variables);

            return R.ok(
                    FlowBizKeyDto
                            .builder()
                            .instanceId(processInstance.getProcessInstanceId())
                            .businessKey(businessKey)
                            .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("流程启动错误");
        }
    }

    /**
     * 根据流程定义Key启动流程实例
     * 流程实例绑定businessKey
     *
     * @param procDefKey
     * @param variables
     * @return 返回第一个用户任务  FlowStartDto flowStartDto
     */
    @Override
    public R<FlowTaskDto> startProcessInstanceByKey(String procDefKey, String businessKey, Map<String, Object> variables) {
        log.info("startProcessInstanceByKey预启动流程,procDefKey:{},businessKey:{}", procDefKey, businessKey);
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey)
                    .latestVersion().singleResult();
            if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
                return R.fail("流程已被挂起,请先激活流程");
            }

            identityService.setAuthenticatedUserId("admin");
            if (ObjectUtil.isEmpty(variables.get(ProcessConstants.PROCESS_INITIATOR)))
                variables.put(ProcessConstants.PROCESS_INITIATOR, "admin");

            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(procDefKey, businessKey, variables);

            String processInstanceId = processInstance.getProcessInstanceId();
            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            FlowTaskDto flowTaskDto = new FlowTaskDto();
            String id = task.getId();
            flowTaskDto.setTaskId(id);
            flowTaskDto.setProcInsId(processInstanceId);
            return R.ok(flowTaskDto);
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("流程启动错误");
        }
    }

    @Override
    public R<Boolean> stopProcessInstanceByKey(String businessKey) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey)
                .singleResult();

        if (processInstance != null) {
            runtimeService.deleteProcessInstance(processInstance.getProcessInstanceId(), "");
            return R.ok(true);
        }
        return R.fail(false);
    }

    @Override
    public AjaxResult startProcessByKeyWithBusinessKey(FlowStartDto flowStartDto) {
        log.info("startProcessByKeyWithBusinessKey流程启动参数,flowStartDto:{}",flowStartDto);
        String procDefKey = flowStartDto.getProcDefKey();
        String businessKey = flowStartDto.getBusinessKey();
        Map<String, Object> variables = flowStartDto.getVariables();
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey)
                    .latestVersion().singleResult();
            if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
                return AjaxResult.error("流程已被挂起,请先激活流程");
            }
            List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().
                    processDefinitionKey(procDefKey).processInstanceBusinessKey(businessKey).list();
            if(list == null || !list.isEmpty()){
                return AjaxResult.error("businessKey在当前定义的流程中已存在");
            }
            //这段没明确作用
            identityService.setAuthenticatedUserId("admin");
            if (ObjectUtil.isEmpty(variables.get(ProcessConstants.PROCESS_INITIATOR))) {
                variables.put(ProcessConstants.PROCESS_INITIATOR, "admin");
            }

            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(procDefKey, businessKey, variables);
            return AjaxResult.success(processInstance.getProcessInstanceId());
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("流程启动失败,检查流程初始化变量");
        }
    }


    /**
     * 激活或挂起流程定义
     *
     * @param state    状态
     * @param deployId 流程部署ID
     */
    @Override
    public void updateState(Integer state, String deployId) {
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 激活
        if (state == 1) {
            repositoryService.activateProcessDefinitionById(procDef.getId(), true, null);
        }
        // 挂起
        if (state == 2) {
            repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null);
        }
    }

    /**
     * 激活流程定义
     *
     * @param deployId 流程部署ID
     */
    @Override
    public void activationProcess(String deployId) {
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        repositoryService.activateProcessDefinitionById(procDef.getId(), true, null);

    }

    /**
     * 挂起流程定义
     *
     * @param deployId 流程部署ID
     */
    @Override
    public void suspendProcess(String deployId) {
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null);
    }


    /**
     * 删除流程定义
     *
     * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值
     */
    @Override
    public void delete(String deployId) {
        // true 允许级联删除 ,不设置会导致数据库外键关联异常
        repositoryService.deleteDeployment(deployId, true);
    }

    /**
     * 预警工单启动流程实例
     *
     * @param procDefId 流程模板ID
     * @param variables 流程变量
     * @return
     */
    @Override
    public R<FlowTaskDto> earlyWarningTicketsStart(String procDefId, Map<String, Object> variables) {
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
                    .latestVersion().singleResult();
            if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
                return R.fail("流程已被挂起,请先激活流程");
            }

            identityService.setAuthenticatedUserId("admin");
            if (ObjectUtil.isEmpty(variables.get(ProcessConstants.PROCESS_INITIATOR)))
                variables.put(ProcessConstants.PROCESS_INITIATOR, "admin");

            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);

            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).list().get(0);
            FlowTaskDto flowTaskDto = new FlowTaskDto();
            flowTaskDto.setTaskId(task.getId());
            flowTaskDto.setProcInsId(processInstance.getProcessInstanceId());

            return R.ok(flowTaskDto);
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("流程启动错误");
        }
    }


}
